﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;

using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Documents.Extensions;
using Lucene.Net.Index;
using Lucene.Net.Queries.Function;
using Lucene.Net.Search;
using Lucene.Net.Spatial;
using Lucene.Net.Spatial.Prefix;
using Lucene.Net.Spatial.Prefix.Tree;
using Lucene.Net.Spatial.Queries;
using Lucene.Net.Store;
using Lucene.Net.Util;

using NetTopologySuite.Operation.Distance;

using Spatial4n.Context;
using Spatial4n.Distance;
using Spatial4n.Shapes;

namespace iTool.Cloud.DataSearch
{
    public class CityGeoInfo 
    {
        public CityGeoInfo(string c, double x, double y) 
        {
            this.City = c;
            this.X= x;
            this.Y= y;
        }

        public string City { get; set; }
        public double X { get; set; }
        public double Y { get; set; }

        public static List<CityGeoInfo> GetCityGeoInfos() 
        {
            List<CityGeoInfo> cityGeoInfos= new List<CityGeoInfo>();    
            var lines = File.ReadAllLines("./geotestdata.text");
            foreach (var line in lines)
            {
                var array = line.Split(',');
                cityGeoInfos.Add(new CityGeoInfo(array[0], double.Parse(array[1]), double.Parse(array[2])));
            }
            return cityGeoInfos;
        }

        public override string ToString()
        {
            return string.Format("insert into Locations(city,x,y) values('{0}',{1},{2})", this.City,this.X,this.Y);
        }

        public string ToString(int index)
        {
            return string.Format("insert into Locations(city,x,y,index) values('{0}',{1},{2},{3})", this.City, this.X, this.Y, index);
        }

    }

    public class SimpleGeoDirectory
    {
        static SpatialContext ctx = SpatialContext.Geo;
        static SpatialPrefixTree spatialPrefixTree = new GeohashPrefixTree(ctx, 11);
        static SpatialStrategy strategy = new RecursivePrefixTreeStrategy(spatialPrefixTree, "location");
        static RAMDirectory directory = new RAMDirectory();

        public static void Writer(List<CityGeoInfo> cityGeoInfos)
        {
            IndexWriterConfig config = new IndexWriterConfig(LuceneVersion.LUCENE_48, new StandardAnalyzer(LuceneVersion.LUCENE_48));
            IndexWriter indexWriter = new IndexWriter(directory, config);
            indexWriter.AddDocuments(NewSampleDocument(ctx, strategy, cityGeoInfos));
            indexWriter.Dispose();
        }

        /// <summary>
        /// 距离搜索
        /// </summary>
        /// <param name="inDistance">多少距离内</param>
        public static void Search(double x, double y, int inDistance)
        {

            IPoint iPoint = ctx.MakePoint(x, y);

            IndexReader indexReader = DirectoryReader.Open(directory);
            IndexSearcher searcher = new IndexSearcher(indexReader);

            // 搜索方圆千米范围以内 {inDistance} km,其中半径为 {inDistance} KM
            double distance = DistanceUtils.Dist2Degrees(inDistance, DistanceUtils.EarthMeanRadiusKilometers);
            IShape shape = ctx.MakeCircle(iPoint, distance);
            SpatialArgs args = new SpatialArgs(SpatialOperation.Intersects, shape);

            Query query = strategy.MakeQuery(args);

            // 计算命中坐标距离圆点的距离
            ValueSource valueSource = strategy.MakeDistanceValueSource(iPoint, DistanceUtils.DegreesToKilometers);
            
            // 距离远近降序排； false表示降序,true表示升序
            var field = valueSource.GetSortField(false); 
            Sort distSort = new Sort(field).Rewrite(searcher);

            WildcardQuery termQuery2 = new WildcardQuery(new Term("city", "三*"));
            WildcardQuery termQuery3 = new WildcardQuery(new Term("city", "*州"));
            BooleanQuery booleanQuery = new BooleanQuery();
            booleanQuery.Add(query, Occur.SHOULD);
            booleanQuery.Add(termQuery2, Occur.SHOULD);
            booleanQuery.Add(termQuery3, Occur.SHOULD);

            // SortFieldType.SINGLE 必须是 int
            // SortFieldType.SCORE 匹配度
            // SortFieldType.STRING 字符串

            //reverse 为true (desc)是降序  false (asc)为升序

            var sortxx = new Sort(new List<SortField> { new SortField("sortx", SortFieldType.DOUBLE, true), new SortField("sortd", SortFieldType.STRING, true) }.ToArray());
            sortxx = new Sort(new SortField("sortx", SortFieldType.DOUBLE, true));
            sortxx = new Sort(new SortField("sortd", SortFieldType.DOUBLE, true));
            sortxx = distSort;

            TopDocs topDocs = searcher.SearchAfter(null,booleanQuery, 2, sortxx);

            // 查询所属有
            //TopDocs topdocs = searcher.Search(new MatchAllDocsQuery(), 10, distSort);

            FieldDoc scoreDoc = (FieldDoc)printDocument(topDocs, searcher, iPoint);


            Console.Clear();
            while (scoreDoc != null)
            {
                Console.WriteLine("Next Fields Type:{0}", scoreDoc.Fields[0].GetType().FullName);
                Console.WriteLine("Next Page:{0}", scoreDoc.ToString());
                topDocs = searcher.SearchAfter(scoreDoc, booleanQuery, 2, sortxx);
                scoreDoc = (FieldDoc)printDocument(topDocs, searcher, iPoint);

                if (scoreDoc == null)
                {
                    break;
                }
                var json = new SearchScoreDocItemOptions(scoreDoc).ToString();
                scoreDoc = new SearchScoreDocItemOptions(json).GetFieldDocItem();
            }

        }

        static List<Document> NewSampleDocument(SpatialContext ctx, SpatialStrategy strategy, List<CityGeoInfo> cityGeoInfos)
        {
            var structValueFieldType = new FieldType
            {
                IsStored = false,
                IsIndexed = true,
                IsTokenized = false
            };

            int index = 0;
            List<Document> documents = cityGeoInfos.Select(cgi =>
            {
                index++;
                Document doc = new Document();
                doc.Add(new StringField("city", cgi.City, Field.Store.YES));

                doc.Add(new Field("sortx", index.ToString(), structValueFieldType));
                doc.Add(new Field("sortd", DateTime.Now.AddDays(index).Ticks.ToString(), structValueFieldType));

                doc.Add(new StringField("indexValue", index.ToString(), Field.Store.YES));
                doc.Add(new StringField("indexDate", DateTime.Now.AddDays(index).ToString(), Field.Store.YES));

                IPoint shape = null;
                if (index % 10 != 0)
                {
                    shape = ctx.MakePoint(cgi.X, cgi.Y);
                    Field[] fields = strategy.CreateIndexableFields(shape);
                    foreach (Field field in fields)
                    {
                        doc.Add(field);
                    }
                }

                if (shape != null)
                {
                    // 应该是可以不用的
                    doc.Add(new StoredField(strategy.FieldName + "xxx", shape.X + "," + shape.Y));
                }

                return doc;

            }).ToList();

            return documents;
        }

        static ScoreDoc printDocument(TopDocs topdocs, IndexSearcher searcher, IPoint point) 
        {
            Console.WriteLine("TotalHits: " + topdocs.TotalHits);
            if (topdocs.ScoreDocs.Count() == 0)
            {
                return null;
            }

            ScoreDoc[] scoreDocs = topdocs.ScoreDocs;
            foreach (var scoreDoc in topdocs.ScoreDocs)
            {
                int docId = scoreDoc.Doc;
                Document document = searcher.Doc(docId);
                String city = document.GetField("city").GetStringValue();
                String index = document.GetField("indexValue").GetStringValue();
                String indexDate = document.GetField("indexDate").GetStringValue();


                // 计算距离
                String? location = document.GetField(strategy.FieldName + "xxx")?.GetStringValue();

                double distance = 0;
                if (location != null)
                {
                    String[] locations = location.Split(",");
                    double xPoint = Double.Parse(locations[0]);
                    double yPoint = Double.Parse(locations[1]);
                    double distDEG = ctx.CalcDistance(point, xPoint, yPoint);
                    distance = DistanceUtils.Degrees2Dist(distDEG, DistanceUtils.EarthMeanRadiusKilometers);
                }
                
                Console.WriteLine(string.Format("docId:{0},city:{1},distance:{2}KM,index:{3},indexDate:{4}", docId, city, distance, index, indexDate));
            }

            return topdocs.ScoreDocs.Last();
        }
    }
}
